/*
** xfconf-channel.c
*/
#include <stdio.h>
#include <string.h>
#include <gtk/gtk.h>
#include <glib.h>
#include <glib/gprintf.h>
#include <errno.h>
#include <stdlib.h>

#include "xfconf-channel.h"
#include "xfce-fake.h"


G_DEFINE_TYPE(XfconfChannel, xfconf_channel, G_TYPE_OBJECT)



#define XFCONF_CHANNEL_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), XFCONF_TYPE_CHANNEL, XfconfChannelPrivate))

struct _XfconfChannelPrivate
{
    gchar *channel_name_and_base;
    GHashTable * entries;
    GSList * bindings;
    gulong counter;
    XfceRc * rc_file;
};

typedef struct
{
    gulong id;
    XfconfChannel *channel;
    gchar *xfconf_property;
    GType xfconf_property_type;
    GObject *object;
    gchar *object_property;
    GType object_property_type;
    gulong handler_id;
    GValue * dst_val;
} XfconfGBinding;


static void delete_binding(gpointer data,GObject *where_the_object_was);

static char * confstr_to_gfilestr(const char * name );


static void
hash_table_remove_value(gpointer data)
{
    GValue * a = data;
    g_slice_free(GValue,a);
}

static void
save_func(gpointer mkey, gpointer mvalue, gpointer data)
{
    char * key = mkey;
    GValue * value = mvalue;
    XfceRc * rc_file = data;
    GType typ =G_VALUE_TYPE(value);
    char * towrite = NULL;
    switch( typ ) {
    case G_TYPE_BOOLEAN:
      towrite=g_strdup_printf("B%d",g_value_get_boolean(value)?1:0);
      break;
    case G_TYPE_UINT:
      towrite=g_strdup_printf("U%u",g_value_get_uint(value));
      break;
    default:
      g_warning("Not supported yet:%s\n",g_type_name(typ));
      break;
    }
    if ( towrite ){
	xfce_rc_write_value(rc_file,key,towrite);
	g_free(towrite);
    }
}

static void
xfconf_channel_finalize(XfconfChannel *instance)
{
    XfconfChannelPrivate * priv = XFCONF_CHANNEL_GET_PRIVATE(instance);
    GSList * l ;
    for ( l = priv->bindings; l ; l = l->next ){    
	XfconfGBinding *binding = l->data;
	g_object_weak_unref(G_OBJECT(binding->object),delete_binding,binding);
	g_signal_handler_disconnect(binding->object,binding->handler_id);
	g_free(binding->xfconf_property);
	g_free(binding->object_property);
	g_slice_free(XfconfGBinding,binding);
    }
    g_slist_free(priv->bindings);
    g_hash_table_foreach(priv->entries,save_func,priv->rc_file);
    xfce_rc_close(priv->rc_file);
    
    g_hash_table_destroy(priv->entries);
    G_OBJECT_CLASS(xfconf_channel_parent_class)->finalize(G_OBJECT(instance)); 
}


static void
xfconf_channel_class_init (XfconfChannelClass *klass)
{
    g_type_class_add_private (klass, sizeof (XfconfChannelPrivate));
    G_OBJECT_CLASS(klass)->finalize = (GObjectFinalizeFunc) xfconf_channel_finalize;
}




static void
xfconf_channel_init(XfconfChannel *instance)
{
    XfconfChannelPrivate * priv = XFCONF_CHANNEL_GET_PRIVATE(instance);
    priv->channel_name_and_base =NULL;
    priv->entries=g_hash_table_new_full(g_str_hash,g_str_equal,g_free,hash_table_remove_value);
    priv->bindings = NULL;
    priv->rc_file=NULL;
    priv->counter = 1;
}



XfconfChannel * xfconf_channel_new_with_property_base(const gchar *channel_name,
						      const gchar *property_base)
{
    XfconfChannel *newChannel = g_object_new (XFCONF_TYPE_CHANNEL, NULL);
    XfconfChannelPrivate * priv = XFCONF_CHANNEL_GET_PRIVATE(newChannel);
    char * tmp = g_strconcat(channel_name,property_base,NULL);
    char **keys;
    char **p;
    GValue * new_val;
    priv->channel_name_and_base=confstr_to_gfilestr(tmp);
    g_free(tmp);
    tmp=g_build_path(G_DIR_SEPARATOR_S,g_get_user_config_dir(),ROX_PLUGIN_DOMAIN,ROX_PLUGIN_NAME,priv->channel_name_and_base,NULL);
    priv->rc_file=xfce_rc_simple_open(tmp,FALSE);
    g_free(tmp);
    keys=xfce_rc_get_keys(priv->rc_file);
    if ( keys ){
	for ( p = keys; *p ; ++p ){
	    tmp=xfce_rc_read_value(priv->rc_file,*p);
	    if ( tmp ){
		char * cur = tmp;
		new_val=NULL;

		switch(*cur){
		case 'B':
		  cur++;
		  new_val=g_slice_new0(GValue);
		  g_value_init(new_val,G_TYPE_BOOLEAN);
		  g_value_set_boolean(new_val,*cur == '1'? TRUE : FALSE );
		  break;
		case 'U':
		  {
		      unsigned long long int a;
		      cur++;
		      errno=0;
		      a = strtoull(cur,NULL,10);
		      if ( errno == 0 && a <= (unsigned long long int)UINT_MAX ){
			  new_val=g_slice_new0(GValue);
			  g_value_init(new_val,G_TYPE_UINT);
			  g_value_set_uint(new_val,(uint)a);
		      }
		  }
		  break;
		default:
		  g_warning("Format not supported yet\n");
		}

		if ( new_val != NULL ){
		    g_hash_table_insert(priv->entries,g_strdup(*p),new_val);
		}

	    }
	    g_free(tmp);
	}
	g_strfreev(keys);
    }
    return newChannel;
}

static void dirty_transform(GValue * from , GValue * to )
{
    if ( G_VALUE_TYPE(from) == G_TYPE_DOUBLE ){
	double mdouble = g_value_get_double(from);
	if ( G_VALUE_TYPE(to) == G_TYPE_UINT ){
	    g_value_set_uint(to,(uint)mdouble);
	    return ;
	}
    }
    g_warning("transormation not supported yet,....\n");
    g_value_copy(from,to);
}

static void
xfconf_g_binding_object_property_changed(GObject *object,
                                         GParamSpec *pspec,
                                         gpointer user_data)
{
    XfconfGBinding *binding = user_data;
    XfconfChannelPrivate * priv = XFCONF_CHANNEL_GET_PRIVATE(binding->channel);
    GValue  src_val = { 0, };
    GValue * dst_val = binding->dst_val;
    GSList * l;

    /* this can do auto-conversion for us, but we can't easily tell if
     * the conversion worked */
    g_value_init(&src_val, G_PARAM_SPEC_VALUE_TYPE(pspec));
    g_object_get_property(object, g_param_spec_get_name(pspec), &src_val);

    if(g_value_transform(&src_val,dst_val)) {
	if ( binding->object_property_type == binding->xfconf_property_type ){
	    *dst_val=src_val;
	}
	else {
	    dirty_transform(&src_val,dst_val);
	}
	for ( l = priv->bindings; l ; l = l->next ){
	    XfconfGBinding *cur_binding=l->data;
	    if ( cur_binding == binding ){
		continue;
	    }
	    if ( *cur_binding->xfconf_property == *binding->xfconf_property &&
		 strcmp(cur_binding->xfconf_property,binding->xfconf_property)== 0 
		)
	    {
		g_signal_handlers_block_by_func(G_OBJECT(cur_binding->object),
						G_CALLBACK(xfconf_g_binding_object_property_changed),
						cur_binding);
		g_object_set_property(G_OBJECT(cur_binding->object), cur_binding->object_property, dst_val);
		g_signal_handlers_unblock_by_func(G_OBJECT(cur_binding->object),
						  G_CALLBACK(xfconf_g_binding_object_property_changed),
						  cur_binding);
	    }
	}
    }
    g_value_unset(&src_val);
}



static void
delete_binding(gpointer data,GObject *where_the_object_was)
{
    XfconfGBinding *binding = data;
    XfconfChannelPrivate * priv = XFCONF_CHANNEL_GET_PRIVATE(binding->channel);
    (void)where_the_object_was;
    priv->bindings=g_slist_remove(priv->bindings,binding);
    g_free(binding->xfconf_property);
    g_free(binding->object_property);
    g_slice_free(XfconfGBinding,binding);
}



#define YSTR(s) #s
#define XXSTR(s) YSTR(s)

static char *
confstr_to_gfilestr(const char * name ){
    char * new_name ;
    const char * p ;
    char * q ;
    g_return_val_if_fail(name != NULL && *name != '\0', NULL);
    new_name = g_malloc(strlen(name) +sizeof( XXSTR(UINT_MAX)) );
    for ( p = name, q=new_name ; *p != '\0' ; ++p ){
	if ( g_ascii_isalnum(*p) ){
	    *q++=*p;
	}
    }
    g_sprintf(q,"%u",g_str_hash(name));
    return new_name;
}



/**
 * xfconf_g_property_bind:
 * @channel: An #XfconfChannel.
 * @xfconf_property: A property on @channel.
 * @xfconf_property_type: The type of @xfconf_property.
 * @object: A #GObject.
 * @object_property: A valid property on @object.
 *
 * Binds an Xfconf property to a #GObject property.  If the property
 * is changed via either the #GObject or Xfconf, the corresponding
 * property will also be updated.
 *
 * Note that @xfconf_property_type is required since @xfconf_property
 * may or may not already exist in the Xfconf store.  The type of
 * @object_property will be determined automatically.  If the two
 * types do not match, a conversion will be attempted.
 *
 * Returns: an ID number that can be used to later remove the
 *          binding.
 **/
gulong
xfconf_g_property_bind(XfconfChannel *channel,
                       const gchar *xfconf_property,
                       GType xfconf_property_type,
                       gpointer object,
                       const gchar *object_property)
{
    XfconfGBinding *binding;
    GParamSpec *pspec;
    gchar buf[256];
    GValue * value;
    XfconfChannelPrivate * priv = XFCONF_CHANNEL_GET_PRIVATE(channel);
    char * hashstr ;

    g_return_val_if_fail(XFCONF_IS_CHANNEL(channel)
                         && xfconf_property && *xfconf_property
                         && xfconf_property_type != G_TYPE_NONE
                         && xfconf_property_type != G_TYPE_INVALID
                         && G_IS_OBJECT(object) && !XFCONF_IS_CHANNEL(object)
                         && object_property && *object_property,
                         0UL);

    pspec = g_object_class_find_property(G_OBJECT_GET_CLASS(object),
                                         object_property);
    if(!pspec) {
        g_warning("Property \"%s\" is not valid for GObject type \"%s\"",
                  object_property, G_OBJECT_TYPE_NAME(object));
        return 0UL;
    }

    if(!g_value_type_transformable(xfconf_property_type,
                                   G_PARAM_SPEC_VALUE_TYPE(pspec)))
    {
        g_warning("Converting from type \"%s\" to type \"%s\" is not supported",
                  g_type_name(xfconf_property_type),
                  g_type_name(G_PARAM_SPEC_VALUE_TYPE(pspec)));
        return 0UL;
    }

    if(!g_value_type_transformable(G_PARAM_SPEC_VALUE_TYPE(pspec),
                                   xfconf_property_type))
    {
        g_warning("Converting from type \"%s\" to type \"%s\" is not supported",
                  g_type_name(G_PARAM_SPEC_VALUE_TYPE(pspec)),
                  g_type_name(xfconf_property_type));
        return 0UL;
    }

    binding = g_slice_new0(XfconfGBinding);
    binding->channel = channel;
    binding->xfconf_property_type = xfconf_property_type;
    binding->xfconf_property = g_strdup(xfconf_property);
    binding->object = object;
    binding->object_property = g_strdup(object_property);
    binding->object_property_type = G_PARAM_SPEC_VALUE_TYPE(pspec);


    binding->id=priv->counter++;


    hashstr=confstr_to_gfilestr(xfconf_property);
    value=g_hash_table_lookup(priv->entries,hashstr);
    if ( value ){
	g_object_set_property(object, object_property, value);
	g_free(hashstr);
    }
    else {
	value=g_slice_new0(GValue);
	g_value_init(value,xfconf_property_type);
	g_object_get_property(object,object_property,value);
	g_hash_table_insert(priv->entries,hashstr,value);
    }
    binding->dst_val=value;
    
    g_snprintf(buf, sizeof(buf), "notify::%s", object_property);

    binding->handler_id = g_signal_connect(G_OBJECT(binding->object), buf,
					   G_CALLBACK(xfconf_g_binding_object_property_changed),
					   binding);
 
    g_object_weak_ref(G_OBJECT(binding->object),delete_binding,binding);
    priv->bindings =g_slist_prepend(priv->bindings,binding);

    return binding->id;
}
